commonlibsse_ng\re\b/
ButtonEvent.rs

1use core::alloc::Layout;
2use core::ptr;
3
4use crate::re::BSFixedString::BSFixedString;
5use crate::re::IDEvent::{IDEvent, IDEventVtbl};
6use crate::re::InputDevices::INPUT_DEVICE;
7use crate::re::InputEvent::{INPUT_EVENT_TYPE, InputEvent};
8use crate::re::MemoryManager::alloc::alloc_zeroed;
9use crate::re::TESBox::TESBox;
10use crate::re::offsets_rtti::RTTI_ButtonEvent;
11use crate::re::offsets_vtable::VTABLE_ButtonEvent;
12use crate::rel::ResolvableAddress;
13use crate::rel::id::{DataBaseError, VariantID};
14use crate::rel::module::is_vr;
15use crate::rel::relocation::{RelocationError, relocate_member, relocate_member_mut};
16
17#[repr(C)]
18#[derive(Debug, PartialEq)]
19pub struct ButtonEvent {
20    pub __base: IDEvent, // 0x00
21}
22const _: () = assert!(core::mem::size_of::<ButtonEvent>() == 0x28);
23
24impl ButtonEvent {
25    /// Address & offset of RTTI for `ButtonEvent`.
26    pub const RTTI: VariantID = RTTI_ButtonEvent;
27
28    /// Address & offset of Virtual function table.
29    pub const VTABLE: [VariantID; 1] = VTABLE_ButtonEvent;
30
31    /// # Errors
32    pub fn vtable() -> Result<&'static ButtonEventVtbl, DataBaseError> {
33        Self::VTABLE[0].address().map(|vtable| unsafe { vtable.cast().as_ref() })
34    }
35
36    /// # Errors
37    ///
38    /// # Panics
39    #[allow(clippy::unwrap_in_result)]
40    pub fn new_boxed(
41        input_device: INPUT_DEVICE,
42        user_event: BSFixedString,
43        id_code: u32,
44        value: f32,
45        held_down_secs: f32,
46    ) -> Result<TESBox<Self>, DataBaseError> {
47        const VR_BUTTON_EVENT_SIZE: usize = 0x30;
48        const TOTAL_SIZE: usize = VR_BUTTON_EVENT_SIZE + size_of::<RUNTIME_DATA>();
49
50        let layout = Layout::from_size_align(TOTAL_SIZE, align_of::<Self>()).expect("Valid layout");
51
52        unsafe {
53            let ptr = alloc_zeroed(layout).cast::<u8>();
54            if ptr.is_null() {
55                #[cfg(feature = "tracing")]
56                tracing::error!("Heap allocation failed");
57                return Err(DataBaseError::Poisoned);
58            }
59
60            // Set up the vtable
61            let vtable = Self::vtable()? as *const ButtonEventVtbl;
62
63            // Construct ButtonEvent in place
64            let event_ptr = ptr.cast::<Self>();
65            ptr::write(
66                event_ptr,
67                Self {
68                    __base: IDEvent {
69                        __base: InputEvent {
70                            vtable: vtable.cast(),
71                            device: input_device,
72                            eventType: INPUT_EVENT_TYPE::Button,
73                            next: None,
74                        },
75                        userEvent: user_event,
76                        idCode: id_code,
77                        pad24: 0,
78                    },
79                },
80            );
81
82            // Initialize runtime data
83            {
84                const SE_BUTTON_EVENT_SIZE: usize = 0x28;
85                let runtime_offset =
86                    if is_vr() { SE_BUTTON_EVENT_SIZE } else { VR_BUTTON_EVENT_SIZE };
87                let runtime_ptr = ptr.add(runtime_offset).cast::<RUNTIME_DATA>();
88                ptr::write(runtime_ptr, RUNTIME_DATA { value, heldDownSecs: held_down_secs });
89            }
90
91            Ok(TESBox::from_raw(event_ptr))
92        }
93    }
94
95    /// Gets fields whose offset is determined at runtime.
96    ///
97    /// # Errors
98    /// This function may return an error if the module's runtime is not available or if any error occurs while fetching the runtime state.
99    /// Specifically, it calls `ModuleState::map_active`, which could result in an error.
100    #[inline]
101    pub fn get_runtime_data(&self) -> Result<&RUNTIME_DATA, RelocationError> {
102        relocate_member(self, 0x28, 0x30)
103    }
104
105    /// Gets mutable fields whose offset is determined at runtime.
106    ///
107    /// # Errors
108    /// This function may return an error if the module's runtime is not available or if any error occurs while fetching the runtime state.
109    /// Specifically, it calls `ModuleState::map_active_mut`, which could result in an error.
110    #[inline]
111    pub fn get_runtime_data_mut(&mut self) -> Result<&mut RUNTIME_DATA, RelocationError> {
112        relocate_member_mut(self, 0x28, 0x30)
113    }
114
115    #[inline]
116    pub fn value(&self) -> Option<f32> {
117        Some(self.get_runtime_data().ok()?.value)
118    }
119
120    #[inline]
121    pub fn held_duration(&self) -> Option<f32> {
122        Some(self.get_runtime_data().ok()?.heldDownSecs)
123    }
124
125    #[inline]
126    pub fn is_pressed(&self) -> bool {
127        match self.value() {
128            Some(value) => value > 0.0,
129            None => false,
130        }
131    }
132
133    #[inline]
134    pub fn is_repeating(&self) -> bool {
135        match self.held_duration() {
136            Some(value) => value > 0.0,
137            None => false,
138        }
139    }
140
141    #[inline]
142    pub fn is_down(&self) -> bool {
143        self.is_pressed() && self.held_duration().is_some_and(|duration| duration > 0.0)
144    }
145
146    #[inline]
147    pub fn is_held(&self) -> bool {
148        self.is_pressed() && self.is_repeating()
149    }
150
151    #[inline]
152    pub fn is_up(&self) -> bool {
153        self.value().is_some_and(|value| value > 0.0) && self.is_repeating()
154    }
155}
156
157pub struct ButtonEventVtbl {
158    pub __base: IDEventVtbl, // 0x00
159}
160
161#[derive(Debug, Clone, Default, PartialEq)]
162pub struct RUNTIME_DATA {
163    pub value: f32,
164    pub heldDownSecs: f32,
165}
166const _: () = assert!(core::mem::size_of::<RUNTIME_DATA>() == 0x8);